﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class provides methods to interpolate a trigonometric polynomial by using the DFT.
    /// For a successful interpolation, the distance between the x values of the t(x) interpolation 
    /// source values have to be even.
    /// </summary>
    [Serializable]
    public class TrigonometricPolynomialDFTInterpolation
    {
        /// <summary>
        /// The sample data for the interpolation of the trigonometric polynomial.
        /// </summary>
        private readonly GeneralVector interpolationData;

        /// <summary>
        /// Initializes a new instance of the <see cref="TrigonometricPolynomialDFTInterpolation"/> class.
        /// </summary>
        /// <param name="interpolationData">The sample data for the interpolation of the trigonometric polynomial.</param>
        public TrigonometricPolynomialDFTInterpolation(GeneralVector interpolationData)
        {
            if (interpolationData == (GeneralVector) null)
            {
                throw new ArgumentNullException("interpolationData");
            }

            this.interpolationData = interpolationData.Copy();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TrigonometricPolynomialDFTInterpolation"/> class.
        /// </summary>
        /// <param name="data">The sample data for the interpolation of the trigonometric polynomial.</param>
        public TrigonometricPolynomialDFTInterpolation(double[] data)
        {
            if (data == (double[]) null)
            {
                throw new ArgumentNullException("data");
            }

            this.interpolationData = new GeneralVector((double[]) data.Clone());
        }

        /// <summary>
        /// Interpolates the trigonometric polynomial by using a DFT. The distance between the x 
        /// values of the t(x) interpolation source values have to be even.
        /// </summary>
        /// <returns>The interpolated trigonometric polynomial.</returns>
        public TrigonometricPolynomial InterpolateTrigonometricPolynomial()
        {
            return this.InterpolateTrigonometricPolynomial(this.interpolationData.Count);
        }

        /// <summary>
        /// Interpolates the trigonometric polynomial by using a DFT. The distance between the x 
        /// values of the t(x) interpolation source values have to be even.
        /// </summary>
        /// <param name="maxCoefficients">The maximum coefficients to interpolate.</param>
        /// <returns>
        /// The interpolated trigonometric polynomial.
        /// </returns>
        public TrigonometricPolynomial InterpolateTrigonometricPolynomial(int maxCoefficients)
        {
            if (maxCoefficients <= 0)
            {
                throw new ArgumentNullException("maxCoefficients");
            }

            int numberOfSamples = this.interpolationData.Count;
            double omega = 2 * Math.PI / numberOfSamples;
            int maxIterations = Math.Min(numberOfSamples / 2, maxCoefficients);
            double[] a = new double[maxIterations];
            double[] b = new double[maxIterations];
            TrigonometricPolynomial result = new TrigonometricPolynomial();

            for (int k = 0; k < maxIterations; k++)
            {
                for (int j = 0; j < numberOfSamples; j++)
                {
                    a[k] += this.interpolationData.VectorData[j] * Math.Cos(omega * k * j);
                    b[k] += this.interpolationData.VectorData[j] * Math.Sin(omega * k * j);
                }

                a[k] = (a[k] * 2) / numberOfSamples;
                b[k] = (b[k] * 2) / numberOfSamples;
            }

            double a0 = 0;

            for (int j = 0; j < numberOfSamples; j++)
            {
                a0 += this.interpolationData.VectorData[j];
            }

            a0 /= numberOfSamples;

            if (maxIterations + 1 == numberOfSamples / 2)
            {
                a[maxIterations] = 0;
                double s = -1;

                for (int j = 0; j < numberOfSamples; j++)
                {
                    s = -s;
                    a[maxIterations] += s * this.interpolationData.VectorData[j];
                }

                a[maxIterations] /= numberOfSamples;
            }

            result.A0 = a0 * 2;

            for (int i = 1; i < maxIterations; i++)
            {
                result.AddComponent(new TrigonometricPolynomialComponent(a[i], b[i]));
            }

            if (maxIterations >= (maxIterations / 2))
            {
                result[maxIterations - 2].B = 0;
            }

            return result;
        }
    }
}